<?php
namespace Common\Service;

use Think\Exception;

class WxpayService
{
    private $wxpay_config;

    private $reply;

    public function __construct($type=1)
    {
        vendor('Wxpay.WxPay#Config');
        //不同类型加载不同配置文件
        if($type==1){
            $this->wxpay_config = C('WX_USER_PAY');
        }else{
            $this->wxpay_config = C('WX_BUSINESS_PAY');
        }
        \WxPayConfig::$appId = $this->wxpay_config['appId'];
        \WxPayConfig::$mchId = $this->wxpay_config['mchId'];
        \WxPayConfig::$key = $this->wxpay_config['key'];
        \WxPayConfig::$appSecret = $this->wxpay_config['appSecret'];
        \WxPayConfig::$sslcertPath = $this->wxpay_config['sslcertPath'];
        \WxPayConfig::$sslkeyPath = $this->wxpay_config['sslkeyPath'];
        \WxPayConfig::$caPath = $this->wxpay_config['caPath'];
        vendor('Wxpay.WxPay#Data');
        vendor('Wxpay.WxPay#Api');
    }


    /**
     * app支付
     * @param $outTradeNo string 订单号
     * @param $body string 商品描述
     * @param $totalFee int 订单金额
     * @param int $attach int 1是订单，2是活动订单，3是需求
     * @return array|bool
     * @throws \WxPayException
     */
    public function appPay($outTradeNo ,$body, $totalFee,$attach=1)
    {
        $input = new \WxPayUnifiedOrder();
        $input->SetBody($body);
        $input->SetOut_trade_no($outTradeNo);
        $input->SetTotal_fee( (floatval($totalFee) * 100) );
        $input->SetNotify_url($this->wxpay_config['notifyUrl']);
        $input->SetTrade_type("APP");
        $input->SetAttach($attach);

        //发起请求
        $result = \WxPayApi::unifiedOrder($input);
        if($result['return_code']=='SUCCESS'){
            if($result['result_code']=='SUCCESS'){
                unset($result["sign"]);
                $timeStamp = time();
                $sign_arr = array(
                    "appid" => $result["appid"],
                    "partnerid" => $result["mch_id"],
                    "prepayid" => $result["prepay_id"],
                    "noncestr" => $result["nonce_str"],
                    "timestamp" => $timeStamp,
                    "package" => "Sign=WXPay"
                );
                ksort($sign_arr);
                $string = $this->ToUrlParams($sign_arr);
                //签名步骤二：在string后加入KEY
                $string = $string . "&key={$this->wxpay_config['key']}";
                //签名步骤三：MD5加密
                $string = md5($string);
                //签名步骤四：所有字符转为大写
                $str_sign = strtoupper($string);
                //拼接结果
                $return["sign"] = $str_sign;
                $return["timestamp"] = strval($timeStamp);
                $return["package"] = $sign_arr["package"];
                $return["partnerid"] = $sign_arr["partnerid"];
                $return["appid"] = $sign_arr["appid"];
                $return["prepayid"] = $sign_arr["prepayid"];
                $return["noncestr"] = $sign_arr["noncestr"];
                $return['sn']=$outTradeNo;
                return $return;
            }else{
                return false;
            }
        }else{
            return false;
        }
    }

    /**
     * 企业付款
     * @param $outTradeNo
     * @param $amount
     * @param $openid
     * @param string $desc
     * @param string $check_name
     * @return bool
     * @throws \WxPayException
     */
    public function mchPay($outTradeNo ,$amount,$openid,$desc='转账',$check_name='NO_CHECK'){
        $input = new \mchPay();
        $input->SetOut_trade_no($outTradeNo);
        $input->SetAmount( (floatval($amount) * 100) );
        $input->SetOpenid($openid);
       // $input->SetNotify_url($this->wxpay_config['notifyUrl']);
        $input->SetDesc($desc);
        $input->SetCheck_name($check_name);

        try{
            //发起请求
            $result = \WxPayApi::unifiedOrder($input,6,true);
            if($result['return_code']=='SUCCESS'){
                if($result['result_code']=='SUCCESS'){
                    return true;
                }else{
                    return false;
                }
            }else{
                return false;
            }
        }catch(\WxPayException $e){
            return false;
        }
    }

    /**
     * 格式化参数格式化成url参数
     */
    public function ToUrlParams($sign_arr)
    {
        $buff = "";
        foreach ($sign_arr as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }

        $buff = trim($buff, "&");
        return $buff;
    }

    //订单查询
    public function queryOrder($sn)
    {
        $input = new \WxPayOrderQuery();
        $input->SetOut_trade_no($sn);
        $result = \WxPayApi::orderQuery($input);
        if (array_key_exists("return_code", $result)
            && array_key_exists("result_code", $result)
            && $result["return_code"] === "SUCCESS"
            && $result["result_code"] === "SUCCESS"
            && $result["trade_state"] === 'SUCCESS'
        ) {
            return $result;
        }
        return false;
    }

    /**
     *
     * 回调入口
     * @param bool $needSign 是否需要签名输出
     */
    final public function Handle($needSign = true)
    {
        $msg = "OK";
        $this->reply = new \WxPayNotifyReply();
        //当返回false的时候，表示notify中调用NotifyCallBack回调失败获取签名校验失败，此时直接回复失败
        $result = \WxPayApi::notify(array($this, 'NotifyCallBack'), $msg);
        if ($result == false) {
            $this->reply->SetReturn_code("FAIL");
            $this->reply->SetReturn_msg($msg);
            $this->ReplyNotify(false);
        } else {
            //该分支在成功回调到NotifyCallBack方法，处理完成之后流程
            $this->reply->SetReturn_code("SUCCESS");
            $this->reply->SetReturn_msg("OK");
        }
        $this->ReplyNotify($needSign);
    }

    /**
     *
     * 回调方法入口
     * 注意：
     * 1、微信回调超时时间为2s，建议用户使用异步处理流程，确认成功之后立刻回复微信服务器
     * 2、微信服务器在调用失败或者接到回包为非确认包的时候，会发起重试，需确保你的回调是可以重入
     * @param array $data 回调解释出的参数
     * @param string $msg 如果回调处理失败，可以将错误信息输出到该方法
     * @return true回调出来完成不需要继续回调，false回调处理未完成需要继续回调
     */
    private function NotifyProcess($data, &$msg)
    {
        $log=json_encode($data);
        \Think\Log::record($log,'ALERT');
        //判断通信和业务状态
        if ($data["return_code"] !== "SUCCESS" || $data['result_code'] !== 'SUCCESS') {
            return false;
        }
        $sn = $data['out_trade_no'];
        $type=intval($data['attach']);
        switch ($type){
            case 1 :  $order = lt_get_order_by_sn($sn);break;
            case 2 :  $order = lt_get_activity_order_by_sn($sn);break;
            case 3 :  $order = get_require_order_by_sn($sn);break;
            case 4 :  $order = get_scan_pay_by_sn($sn);break;/*扫一扫支付*/
            default : $order =false;
        }
        if (!$order) {
            return false;
        }
        if ($order['status'] == 1) {
            return true;
        }
        $total_fee = intval($data['total_fee']) / 100;
        $mch_id = $data['mch_id'];
        $userWx=C('WX_USER_PAY')['mchId'];
        $businessWx=C('WX_BUSINESS_PAY')['mchId'];
        if($mch_id!=$userWx && $mch_id!=$businessWx){
            return false;
        }

        if (!isset($order["category"])) {
            $order["category"]=1;
        }

       // if ($order['total'] == 1 && $order['status'] == 0) {
        if ($order['total'] == $total_fee && $order['status'] == 0) {
            $orderData['payment_trade_no'] = $data['transaction_id'];
            $orderData['paid_time'] = strtotime($data['time_end']);
            $orderData['status'] = 1;
            $orderData['bank'] = $data['bank_type'];
            //1为订单，2是活动，3是需求, 4是扫一扫支付
            switch ($type){
                case 1: $result=$this->orderNotify($order,$orderData);break;
                case 2: $result=$this->orderActivityNotify($order,$orderData);break;
                case 3: $result=$this->requireNotify($order,$orderData);break;
                case 4: $result=$this->scanPayNotify($order,$orderData);break;
                default :$result=false;
            }
            return $result;
        } else {
            return false;
        }
    }

    //订单处理
    public function orderNotify($order,$orderData){
        $service=M('BusinessService')->where(['id'=>$order['business_service_id']])->field('business_id')->find();
        if(!$service){
            return false;
        }
        //商户推送
        lt_check_and_push($service['business_id']);
        //开启事务
        M('Order')->startTrans();
        try {
            //插入订单表
            $result=M('Order')->where(['id'=>$order['id']])->save($orderData);
            if ($result === false) {
                throw new Exception('插入订单表失败！');
            }
            //插入价格往来表
            $result=M('PriceLog')->add(['user_id'=>$order['user_id'],'receive_user_id'=>$service['business_id'],'price'=>$order['total'],'create_time'=>time(),'type'=>1,'category'=>$order['category']]);
            if ($result === false) {
                throw new Exception('插入价格往来表失败！');
            }
            //转账到商户
            $balance=$this->getBalance($order['total']);
            $result=M('Business')->where(['id'=>$service['business_id']])->setInc('balance',$balance);
            if ($result === false) {
                throw new Exception('转账到商户失败！');
            }
            M('Order')->commit();
            return true;
        } catch (Exception $e) {
            M('Order')->rollback();
            return false;
        }
    }

    //活动订单处理
    public function orderActivityNotify($order,$orderData){
        $activity=M('BusinessActivity')->where(['id'=>$order['business_activity_id']])->field('business_id')->find();
        if(!$activity){
            return false;
        }
        //商户推送
        lt_check_and_push($activity['business_id']);
        //开启事务
        M('OrderActivity')->startTrans();
        try {
            //插入订单表
            $result=M('OrderActivity')->where(['id'=>$order['id']])->save($orderData);
            if ($result === false) {
                throw new Exception('插入订单表失败！');
            }
            //插入价格往来表
            $result=M('PriceLog')->add(['user_id'=>$order['user_id'],'receive_user_id'=>$activity['business_id'],'price'=>$order['total'],'create_time'=>time(),'type'=>1,'category'=>$order['category']]);
            if ($result === false) {
                throw new Exception('插入价格往来表失败！');
            }
            //转账到商户
            $balance=$this->getBalance($order['total']);
            $result=M('Business')->where(['id'=>$activity['business_id']])->setInc('balance',$balance);
            if ($result === false) {
                throw new Exception('转账到商户失败！');
            }
            M('OrderActivity')->commit();
            return true;
        } catch (Exception $e) {
            M('OrderActivity')->rollback();
            return false;
        }
    }


    //需求订单处理
    public function requireNotify($order,$orderData){
        //开启事务
        M('Require_order')->startTrans();
        try {
            $result=M("Require_order")->where(["id"=>$order['id']])->save(["status"=>1]);
            if ($result === false) {
                throw new Exception('需求订单支付失败！');
            }
            $result=M("Require_ans")->where(["id"=>$order['ans_id']])->save(["status"=>1]);
            if ($result === false) {
                throw new Exception('需求订单支付失败！');
            }
            //插入金额往来表
            $result=M('PriceLog')->add(['user_id'=>$order['user_id'],'receive_user_id'=>$order['business_id'],'price'=>$order['total'],'create_time'=>time(),'type'=>$order['user_type'],'category'=>$order['category']]);
            if ($result === false) {
                throw new Exception('插入价格往来表失败！');
            }
            //转账到商户
            $balance=$this->getBalance($order['total']);
            $result=M('Business')->where(['id'=>$order['business_id']])->setInc('balance',$balance);
            if ($result === false) {
                throw new Exception('转账到商户失败！');
            }
            M('Require_order')->commit();
            return true;
        } catch (Exception $e) {
            M('Require_order')->rollback();
            return false;
        }
    }

    //扫一扫支付订单处理
    public function scanPayNotify($order,$orderData){
        //开启事务
        M('ScanPay')->startTrans();
        try {
            $result=M("ScanPay")->where(["id"=>$order['id']])->save(["status"=>1]);
            if ($result === false) {
                throw new Exception('扫一扫支付订单支付失败！');
            }
            //插入金额往来表
            $result=M('PriceLog')->add(['user_id'=>$order['user_id'],'receive_user_id'=>$order['receive_user_id'],'price'=>$order['total'],'create_time'=>time(),'type'=>$order['user_type'],'category'=>$order['category']]);
            if ($result === false) {
                throw new Exception('插入价格往来表失败！');
            }
            //转账到商户
            $balance=$this->getBalance($order['total']);
            $result=M('Business')->where(['id'=>$order['receive_user_id']])->setInc('balance',$balance);
            if ($result === false) {
                throw new Exception('转账到商户失败！');
            }
            M('ScanPay')->commit();
            return true;
        } catch (Exception $e) {
            M('ScanPay')->rollback();
            return false;
        }
    }


    /**
     * 扣除手续费
     * @param $total
     * @return mixed
     */
    private function getBalance($total){
        $total=intval($total);
        $balance=$total-sprintf('%.2f',$total*0.006);
        return $balance;
    }

    /**
     * 企业自动转账
     * @param $total int 总价
     * @param $business_id int 商户id
     * @param string $desc 描述
     * @return bool
     */
    private function businessGive($total,$business_id,$desc='转账'){
        $total=intval($total);
        $balance=$this->getBalance($total);
        $business_id=intval($business_id);
        $business=M('Business')->where(['id'=>$business_id])->find();
        if($business){
            $result=false;
            if($business['openid']){
                $sn=sp_get_order_sn();
                $result=(new WxpayService(2))->mchPay($sn, $total,$business['openid'],$desc);
            }
            //企业付款失败，转入余额
            if($result==false){
                $result=M('Business')->where(['id'=>$business_id])->setInc('balance',$balance);
            }
            return $result;
        }else{
            return false;
        }
    }

    /**
     *
     * notify回调方法，该方法中需要赋值需要输出的参数,不可重写
     * @param array $data
     * @return true回调出来完成不需要继续回调，false回调处理未完成需要继续回调
     */
    final public function NotifyCallBack($data)
    {
        $msg = "OK";
        $result = $this->NotifyProcess($data, $msg);
        if ($result == true) {
            $this->reply->SetReturn_code("SUCCESS");
            $this->reply->SetReturn_msg("OK");
        } else {
            $this->reply->SetReturn_code("FAIL");
            $this->reply->SetReturn_msg($msg);
        }
        return $result;
    }

    /**
     *
     * 回复通知
     * @param bool $needSign 是否需要签名输出
     */
    final private function ReplyNotify($needSign = true)
    {
        //如果需要签名
        if ($needSign == true &&
            $this->reply->GetReturn_code() == "SUCCESS"
        ) {
            $this->SetSign();
        }
        \WxPayApi::replyNotify($this->reply->ToXml());
    }
}